Skip to content

Conversation

@standujar
Copy link
Collaborator

@standujar standujar commented Feb 3, 2026

Summary

  • Fixed buildConversationContext to use state.values.recentMessages instead of state.data.recentMessages
  • Removed unused runtime parameter from buildConversationContext function

Problem

The plugin was failing on cloud because it read from state.data.recentMessages, which only exists in the official bootstrap provider. The cloud provider uses a different structure (state.data.messages).

The correct path is state.values.recentMessages — a formatted string available on both cloud and official bootstrap providers.

Test plan

  • Deploy on cloud and verify CREATE_N8N_WORKFLOW action receives correct conversation context
  • Verify the action generates workflow from actual user request, not confirmation message

Summary by CodeRabbit

  • Refactor
    • Simplified conversation context generation by removing unnecessary parameters and streamlining context assembly logic for improved efficiency.

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

Warning

Rate limit exceeded

@standujar has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 4 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

The buildConversationContext function signature was refactored to remove the runtime parameter. Its implementation was simplified to access recentMessages from state.values instead of state.data, and the return value was condensed to directly concatenate recent messages with the current request text.

Changes

Cohort / File(s) Summary
Conversation Context Refactoring
src/actions/createWorkflow.ts, src/utils/context.ts
Removed runtime parameter from buildConversationContext signature; updated call site accordingly. Simplified implementation to access messages from state.values.recentMessages (string) instead of state.data.recentMessages (array), updated falsy check logic, and condensed return value from formatted multi-line context block to simple concatenation of recent messages and current request.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A simpler path the context takes,
No runtime now, just give and takes,
From nested data to values clean,
The conversation flow streamlined and keen,
Less baggage carried, more direct the way! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: updating to use state.values.recentMessages for cloud compatibility, which is the core fix in both modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/conversation-context-state-path

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@standujar standujar force-pushed the fix/conversation-context-state-path branch from ddd31ac to 3344a94 Compare February 3, 2026 22:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/utils/context.ts`:
- Around line 3-10: buildConversationContext can crash when message.content is
undefined; compute a single safe fallback string like const text =
message.content?.text ?? '' at the top of the function and use that variable in
both the empty-case return and the final template return instead of directly
accessing message.content.text, keeping the rest of the logic (recentMessages
check and template) unchanged.

@standujar standujar force-pushed the fix/conversation-context-state-path branch from 3344a94 to 4eec9e8 Compare February 3, 2026 22:13
): string {
const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
const recentMessages = state?.values?.recentMessages as string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Issue: This as string assertion is unsafe. state?.values?.recentMessages could be undefined, a number, an object, or any other type. This will cause runtime failures if the provider passes an unexpected type.

Suggested change
const recentMessages = state?.values?.recentMessages as string;
const recentMessages = state?.values?.recentMessages;
if (!recentMessages || typeof recentMessages !== 'string') {

Also consider adding runtime validation or defensive type checking.

@claude
Copy link

claude bot commented Feb 3, 2026

Critical Issue: Incomplete Migration

This PR has a breaking change that will cause TypeScript compilation errors and runtime failures in other parts of the codebase.

Problem

The function signature was changed from:

buildConversationContext(runtime: IAgentRuntime, message: Memory, state: State | undefined)

To:

buildConversationContext(message: Memory, state: State | undefined)

However, only one call site was updated (createWorkflow.ts:272). The following files still call it with the old signature:

  • src/actions/activateWorkflow.ts:179
  • src/actions/deactivateWorkflow.ts:117
  • src/actions/deleteWorkflow.ts:111
  • src/actions/getExecutions.ts:101

Impact

This will cause:

  1. TypeScript compilation errors - all 4 files will fail type checking
  2. Runtime crashes - if TypeScript checks are bypassed, the function will receive message where it expects state, causing undefined behavior

Required Fix

Before merging, you must update all call sites to use the new signature:

// OLD (will break)
const context = buildConversationContext(runtime, message, state);

// NEW (correct)
const context = buildConversationContext(message, state);

Recommendation

Run npm run build or tsc --noEmit to catch these issues before creating PRs.

} from "../helpers/mockRuntime";
import { describe, test, expect } from 'bun:test';
import { buildConversationContext } from '../../src/utils/context';
import { createMockMessage, createMockState } from '../helpers/mockRuntime';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused Import: createMockRuntime is no longer imported but the test file doesn't need it anymore since the runtime parameter was removed. Good cleanup!

entityId: "user-001",
content: { text: `Message ${i}` },
}));
test('preserves recentMessages formatting from provider', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Test Coverage: While this test verifies that preformatted messages are preserved, there's no test coverage for:

  1. Non-string values in state.values.recentMessages - What happens if it's a number, object, or array? The as string assertion on line 4 of context.ts will silently coerce these.
  2. Edge cases - Empty string, whitespace-only strings, very long strings, strings with special characters/unicode

Consider adding tests like:

test('handles non-string recentMessages gracefully', () => {
  const message = createMockMessage({ content: { text: 'Test' } });
  const state = createMockState({
    values: { recentMessages: 123 }, // number instead of string
  });
  
  const result = buildConversationContext(message, state);
  expect(result).toBe('Test'); // Should fall back to message text
});

@claude
Copy link

claude bot commented Feb 3, 2026

Security & Code Quality Review

Summary

This is a well-structured ElizaOS plugin for n8n workflow automation. The code demonstrates good TypeScript practices and comprehensive test coverage. However, I've identified several critical security issues and code quality concerns that should be addressed before merging.

Critical Security Issues

1. API Key Exposure in Logs

Location: src/index.ts:74

The plugin logs whether the API key is configured. This could leak sensitive information in production environments.

Risk: If logging is verbose or logs are accessible to unauthorized users, this reveals security configuration details.

Recommendation: Remove API key status from logs entirely, or only log at debug level in development.

2. Unvalidated User Input in Workflow Names

Locations: src/utils/generation.ts:250, src/actions/createWorkflow.ts

The workflow name field accepts arbitrary user input without sanitization, which could lead to XSS vulnerabilities, injection attacks, or log injection.

Recommendation: Implement strict input validation and sanitization for workflow names with length limits and character filtering.

3. Missing Rate Limiting

Location: src/utils/api.ts

The API client has no rate limiting or retry logic. This could lead to DoS vulnerabilities, service degradation, or account suspension from n8n API.

Recommendation: Implement exponential backoff and rate limiting.

4. Credential Data Handling

Location: src/utils/credentialResolver.ts:108-112

Credential data from external providers is directly passed to the n8n API without validation.

Risk: If the external provider is compromised or malicious, it could inject arbitrary data into n8n credentials.

Recommendation: Validate credential data structure before passing to API.

High Priority Issues

5. Type Safety: Unsafe Type Assertions

Location: src/utils/api.ts:287-288

Unsafe type assertion returning undefined as T violates TypeScript type safety.

Recommendation: Use proper type guards or modify return type.

6. Error Handling: Silent Failures

Locations: src/utils/credentialResolver.ts:115-120, src/services/n8n-workflow-service.ts:375-379

Cache failures and workflow tagging failures are logged but swallowed.

Recommendation: Consider whether these failures should propagate or be tracked in metrics.

7. Potential Race Condition in Draft Cache

Location: src/actions/createWorkflow.ts:274-281

The draft cache TTL check and delete are not atomic, which could cause race conditions.

Recommendation: Use atomic operations or add mutex locking.

8. Input Validation for Database Operations

Location: src/services/n8n-credential-store.ts:49-56

userId from message.entityId should be validated before database queries.

Recommendation: Add input validation for all database query parameters.

Code Quality Improvements

  • Inconsistent error message formats
  • Magic numbers should be configurable (draft TTL, search limits, keyword limits)
  • Missing comprehensive input validation for LLM outputs
  • Potential memory leak from indefinite draft caching
  • Type assertions instead of proper type guards
  • Missing JSDoc documentation

Testing Recommendations

Consider adding:

  • Security-focused tests (injection, sanitization)
  • Rate limiting tests
  • Concurrent access tests for draft caching

Overall Assessment

This is a well-architected plugin with good separation of concerns and comprehensive functionality. The RAG pipeline for workflow generation is innovative. With the security fixes applied, this will be production-ready.

Estimated effort to address critical issues: 4-6 hours

See inline comments for specific code issues.

@claude
Copy link

claude bot commented Feb 3, 2026

Additional Code Quality Issues

1. Type Safety Concerns

Location: src/utils/context.ts:4

The code uses an unsafe type assertion:

const recentMessages = state?.values?.recentMessages as string;

This assumes recentMessages is always a string, but there's no runtime validation. If a provider passes a different type (number, object, array), the code will fail silently or crash.

Recommendation: Add runtime type checking:

const recentMessages = state?.values?.recentMessages;

if (\!recentMessages || typeof recentMessages \!== 'string') {
  return message.content.text || '';
}

2. Test Coverage Gaps

The new tests cover the happy path but miss edge cases:

  • Non-string values in recentMessages (numbers, objects, arrays)
  • Empty strings vs undefined
  • Very long conversation histories
  • Special characters and unicode handling

3. Missing Documentation

The PR changes the fundamental contract of buildConversationContext but doesn't add JSDoc comments explaining:

  • The expected format of state.values.recentMessages
  • Which providers guarantee this field
  • Fallback behavior when it's missing

Consider adding:

/**
 * Builds conversation context for workflow actions.
 * 
 * @param message - The current user message
 * @param state - Optional state containing recentMessages from the provider
 * @returns Formatted conversation context with current request appended
 * 
 * @remarks
 * Expects state.values.recentMessages to be a pre-formatted string from the provider.
 * Falls back to message.content.text if recentMessages is unavailable.
 */

4. Positive Notes ✓

  • The fix correctly addresses the cloud/bootstrap provider incompatibility
  • Test refactoring is cleaner and more focused
  • Removing unused runtime parameter simplifies the API
  • Version bump is appropriate

@claude
Copy link

claude bot commented Feb 3, 2026

Security Review

No Critical Security Issues Found ✓

I reviewed the changes for common security vulnerabilities:

  • Injection attacks: No new SQL, command, or code injection vectors introduced
  • API key exposure: No changes to credential handling
  • XSS vulnerabilities: Only plain text concatenation, no HTML rendering
  • Authentication/Authorization: No changes to auth flows

Minor Security Consideration

The reliance on state.values.recentMessages as a trusted string could be problematic if:

  1. A malicious provider injects crafted conversation history
  2. The formatted string is later used in prompt injection scenarios

However, this is an existing risk not introduced by this PR. The change actually reduces complexity by delegating formatting to the provider.


Summary

CANNOT MERGE ❌ - Contains breaking changes that will prevent compilation.

Must Fix Before Merge:

  1. ✅ Update all 4 remaining call sites (activateWorkflow.ts, deactivateWorkflow.ts, deleteWorkflow.ts, getExecutions.ts)
  2. ✅ Add runtime type validation for recentMessages
  3. ⚠️ Remove unused IAgentRuntime import

Recommended:

  1. Add JSDoc documentation
  2. Expand test coverage for edge cases
  3. Run npm run build and npm test before pushing

@standujar standujar force-pushed the fix/conversation-context-state-path branch from 4eec9e8 to 2df841c Compare February 3, 2026 22:16
state: State | undefined
): string {
const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking Change Alert: Removing the runtime parameter is a breaking change that affects the function signature. Ensure all callers have been updated.

Code Quality: The change from processing individual messages to receiving pre-formatted strings is good for simplification, but consider:

  1. Adding JSDoc to document the expected format of recentMessages
  2. Adding validation to ensure recentMessages is a string if present

): string {
const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
const recentMessages = state?.values?.recentMessages as string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Issue: Casting state?.values?.recentMessages as string without validation is unsafe. If the value is not a string (e.g., accidentally set to an object or array), this will cause runtime errors.

Recommendation: Add type validation:

const recentMessages = state?.values?.recentMessages;
if (recentMessages && typeof recentMessages !== 'string') {
  logger.warn('recentMessages is not a string, ignoring');
  return message.content.text || '';
}

if (!recentMessages) {
return message.content.text || '';
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Consideration: The pre-formatted recentMessages string is now inserted directly into the context without any sanitization. If this context is used in prompts to LLMs, ensure that:

  1. The provider that creates recentMessages properly sanitizes input
  2. There's no risk of prompt injection through user-controlled message content
  3. Message length is bounded to prevent context overflow

Recommendation: Add length validation:

if (recentMessages.length > 10000) { // reasonable limit
  return `${recentMessages.slice(-10000)}\n\nCurrent request: ${message.content.text || ''}`;
}

test("includes recent messages in context", () => {
const runtime = createMockRuntime({ agentId: "agent-001" });
const message = createMockMessage({ content: { text: "Activate it" } });
test('appends current request to recentMessages', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage: The tests now validate the new string-based format, which is good. However, consider adding tests for:

  1. Malformed input: What happens if recentMessages is not a string?
  2. Very long messages: Test behavior with extremely long recentMessages strings
  3. Special characters: Test with messages containing newlines, control characters, or other special chars
  4. Empty strings vs undefined: Test the difference between recentMessages: '' and recentMessages: undefined

These edge cases are important for robustness in production.

@standujar standujar force-pushed the fix/conversation-context-state-path branch from 2df841c to 3b3cd55 Compare February 3, 2026 22:16
@claude
Copy link

claude bot commented Feb 3, 2026

Code Review Summary

This PR fixes a critical cloud compatibility issue by updating buildConversationContext to use state.values.recentMessages instead of state.data.recentMessages. The change is well-tested and the refactoring improves code simplicity.

✅ Positive Points

  • Critical bug fix: Addresses cloud provider compatibility by using the correct state path
  • Simplified implementation: Removes unnecessary runtime parameter and complex message processing logic
  • Well-tested: Updated tests cover edge cases (undefined state, empty messages, formatted messages)
  • Breaking change handled correctly: Version bump from 1.0.10 → 1.0.11 reflects API change
  • Consistent changes: All 5 action files updated uniformly

⚠️ Issues Found

1. Type Safety Concern (Medium Priority)

  • state?.values?.recentMessages as string uses unsafe type assertion
  • No runtime validation that recentMessages is actually a string
  • Could fail silently if provider returns different type

2. Potential Runtime Error (Medium Priority)

  • If message.content is undefined/null, message.content.text will throw
  • Previous implementation had same issue, but worth fixing

3. Test Coverage Gap (Low Priority)

  • Tests do not verify behavior when recentMessages is non-string type
  • Missing test for message.content being undefined

📝 Recommendations

See inline comments for specific code suggestions. Overall, the PR is good to merge after addressing the type safety issue.

@claude
Copy link

claude bot commented Feb 3, 2026

Code Review Summary

I've reviewed PR #12 which fixes conversation context handling for cloud compatibility. The changes look good overall, but I've identified several items worth discussing.

✅ Positive Changes

  1. Cloud Compatibility Fix: The core fix moving from state.data.recentMessages to state.values.recentMessages is correct and addresses the incompatibility between cloud and bootstrap providers.

  2. Simplified Architecture: Removing the runtime parameter and trusting the provider's pre-formatted messages is a cleaner design that delegates formatting responsibility to the provider layer.

  3. Consistent Updates: All action files properly updated to use the new signature.

⚠️ Issues & Concerns

See inline comments for specific code issues. Key concerns:

  1. Type Safety Regression: The change from Memory[] to string removes type safety but may be necessary given the provider contract
  2. Test Coverage: Tests removed validation of message limiting (5 messages) and role labeling logic
  3. Missing Input Validation: No checks for non-string values in state.values.recentMessages

🔒 Security Review

✅ No security issues identified:

  • No API key exposure risks
  • No injection vulnerabilities introduced
  • String concatenation is safe (no HTML/SQL context)
  • Properly uses optional chaining to prevent null/undefined errors

Recommendation

The core fix is sound and necessary for cloud compatibility. Consider adding runtime type validation for state.values.recentMessages to fail gracefully if the provider doesn't set it correctly.

Approval Status: ✅ Approve with minor suggestions for improvement (see inline comments)

): string {
const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
const recentMessages = state?.values?.recentMessages as string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Issue: The as string type assertion is unsafe. If a provider supplies recentMessages as a non-string type (array, object, etc.), this will fail silently or cause runtime errors when used in template literals.

Recommendation: Add runtime type checking:

const recentMessages = state?.values?.recentMessages;
if (typeof recentMessages !== 'string' || !recentMessages) {
  return message.content.text || '';
}

Or use a type guard for better safety.

.join('\n');

return `Recent conversation:\n${context}\n\nCurrent request: ${message.content.text || ''}`;
return `${recentMessages}\n\nCurrent request: ${message.content.text || ''}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Runtime Error: If message.content is undefined or null, accessing message.content.text will throw a TypeError.

Recommendation: Use optional chaining:

return `${recentMessages}\n\nCurrent request: ${message.content?.text || ''}`;

This matches the defensive pattern used in line 7.

state: State | undefined
): string {
const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good refactoring: Removing the unused runtime parameter simplifies the API and reduces coupling. The function signature is now cleaner and more focused.

): string {
const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
const recentMessages = state?.values?.recentMessages as string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Concern: Casting to string without validation could cause runtime issues if a provider sets this to a non-string value.

Consider adding a runtime type check:

const recentMessages = state?.values?.recentMessages;
if (recentMessages && typeof recentMessages !== 'string') {
  // Log warning or handle gracefully
  return message.content.text || '';
}

This would make the function more defensive against unexpected provider implementations.

@@ -1,22 +1,13 @@
import { type IAgentRuntime, type Memory, type State, type UUID } from '@elizaos/core';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused Import: IAgentRuntime and UUID are no longer used after removing the runtime parameter. Consider cleaning up:

import { type Memory, type State } from '@elizaos/core';

entityId: "user-001",
content: { text: `Message ${i}` },
}));
test('preserves recentMessages formatting from provider', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good test coverage: This test verifies that the function preserves whatever formatting the provider supplies, which is exactly the right behavior. This demonstrates the simplified approach works correctly with different provider implementations.

@standujar standujar force-pushed the fix/conversation-context-state-path branch from 3b3cd55 to f89200f Compare February 3, 2026 22:17
.join('\n');

return `Recent conversation:\n${context}\n\nCurrent request: ${message.content.text || ''}`;
return `${recentMessages}\n\nCurrent request: ${message.content.text || ''}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edge Case: If message.content.text is empty and recentMessages exists, this will result in a string ending with "\n\nCurrent request: ".

Consider:

const currentText = message.content.text || '';
if (!currentText) {
  return recentMessages; // Don't append empty "Current request:"
}
return `${recentMessages}\n\nCurrent request: ${currentText}`;

This would avoid awkward formatting when there's no current request text.

const result = buildConversationContext(message, state);
expect(result).toBe(`${preformattedMessages}\n\nCurrent request: Do something`);
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage: Consider adding tests for edge cases:

  1. Non-string recentMessages: What happens if a provider mistakenly provides an array or object?
test('handles non-string recentMessages gracefully', () => {
  const message = createMockMessage({ content: { text: 'Test' } });
  const state = createMockState({
    values: { recentMessages: ['msg1', 'msg2'] }, // Wrong type
  });
  const result = buildConversationContext(message, state);
  expect(result).toBe('Test'); // Should fallback safely
});
  1. Undefined message.content: Verify it doesn't throw
test('handles undefined message content', () => {
  const message = createMockMessage({ content: undefined });
  const state = createMockState();
  expect(() => buildConversationContext(message, state)).not.toThrow();
});

test("returns message text when no recent messages", () => {
const runtime = createMockRuntime();
describe('buildConversationContext', () => {
test('returns message text when no recent messages in values', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage Gap: The old tests validated important behaviors that are now gone:

  1. Message limiting (5 messages max) - removed in the refactor but not explicitly verified to be unnecessary
  2. Role labeling (User vs Assistant) - now delegated to provider, but should we test the provider contract?

Consider adding a test that verifies behavior when state.values.recentMessages is set to an unexpected type (number, object, etc.) to ensure graceful degradation.

entityId: "user-001",
content: { text: `Message ${i}` },
}));
test('preserves recentMessages formatting from provider', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Test: This test validates that pre-formatted messages from providers are preserved as-is, which is important for the cloud compatibility fix. Nice addition! 👍

However, the test name "preserves recentMessages formatting from provider" could be more specific about what formatting is being preserved (timestamps, custom prefixes, etc.).

const userId = message.entityId;
const cacheKey = `workflow_draft:${userId}`;
const generationContext = buildConversationContext(runtime, message, state);
const generationContext = buildConversationContext(message, state);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistent Update: ✅ Correctly updated to use the new 2-parameter signature. This is the critical path where conversation context is used for workflow generation, so the fix here ensures the LLM gets proper context on both cloud and bootstrap providers.

{
"name": "@elizaos/plugin-n8n-workflow",
"version": "1.0.10",
"version": "1.0.11",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version Bump: ✅ Appropriate patch version bump (1.0.10 → 1.0.11) for a bug fix. This follows semantic versioning correctly.

@claude
Copy link

claude bot commented Feb 3, 2026

Additional Context: Security & Architecture Analysis

🔒 Security Assessment

No new security vulnerabilities introduced. The changes are minimal and focused:

  • No credential exposure: The function only handles message text and state data
  • No injection risks: No dynamic code execution or template injection vectors
  • No API changes: Internal utility function (not exported in public API)

The type safety issue mentioned earlier (unsafe as string assertion) could lead to unexpected behavior but is not a security vulnerability.

🏗️ Architecture Notes

Migration Path Analysis:

  • buildConversationContext is internal only (not exported in src/utils/index.ts)
  • This confirms the patch version bump (1.0.11) is appropriate
  • No breaking changes for plugin consumers

Provider Compatibility:
The PR description correctly identifies the issue:

  • ❌ Old: state.data.recentMessages (bootstrap provider only)
  • ✅ New: state.values.recentMessages (both providers)

This is the right abstraction level - letting providers format messages rather than re-formatting them.

📊 Code Quality Metrics

  • Lines changed: +43 / -104 = 61 lines removed (good simplification)
  • Cyclomatic complexity: Reduced from ~8 to ~2 in buildConversationContext
  • Test coverage: Maintained (4/4 tests pass, though edge cases recommended)

✅ Final Verdict

Approve with minor improvements recommended. The critical bug is fixed, tests pass, and the refactoring improves code quality. Address the type safety issue for production robustness.

const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
const raw = state?.values?.recentMessages;
const recentMessages = typeof raw === 'string' ? raw : '';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Issue: This type check is too narrow. If state.values.recentMessages is not a string (e.g., an array, object, or number), it will be coerced to an empty string, potentially hiding data issues.

Consider explicit validation:

Suggested change
const recentMessages = typeof raw === 'string' ? raw : '';
const raw = state?.values?.recentMessages;
if (typeof raw !== 'string') {
return message.content.text || '';
}
const recentMessages = raw;

This would make unexpected types fail fast during development rather than silently converting to empty string.

.join('\n');

return `Recent conversation:\n${context}\n\nCurrent request: ${message.content.text || ''}`;
return `${recentMessages}\n\nCurrent request: ${message.content.text || ''}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Issue: If message.content.text is falsy but recentMessages exists, this returns just the recent messages without "Current request:" prefix. This could confuse the LLM context.

Consider:

Suggested change
return `${recentMessages}\n\nCurrent request: ${message.content.text || ''}`;
const currentRequest = message.content.text || '';
return currentRequest
? `${recentMessages}\n\nCurrent request: ${currentRequest}`
: recentMessages;

This makes the behavior explicit and avoids an awkward "Current request: " with empty text.

const result = buildConversationContext(message, state);
expect(result).toBe(`${preformattedMessages}\n\nCurrent request: Do something`);
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Test Coverage: The tests don't cover important edge cases:

  1. What happens if state.values.recentMessages is not a string (e.g., array, number, object)?
  2. What happens if message.content.text is falsy but recentMessages exists?
  3. What happens if recentMessages is a very long string (potential token/context issues)?

Consider adding tests like:

test('handles non-string recentMessages gracefully', () => {
  const message = createMockMessage({ content: { text: 'Request' } });
  const state = createMockState({
    values: { recentMessages: ['msg1', 'msg2'] }, // Array instead of string
  });
  const result = buildConversationContext(message, state);
  expect(result).toBe('Request'); // Should fallback safely
});

test('handles empty current message with recentMessages', () => {
  const message = createMockMessage({ content: { text: '' } });
  const state = createMockState({
    values: { recentMessages: 'User: Previous message' },
  });
  const result = buildConversationContext(message, state);
  // Verify expected behavior for this edge case
});

@standujar standujar force-pushed the fix/conversation-context-state-path branch from e7c44b7 to 6b5c542 Compare February 3, 2026 22:19
@claude
Copy link

claude bot commented Feb 3, 2026

Code Review Summary

This PR fixes a critical cloud compatibility issue by switching from state.data.recentMessages to state.values.recentMessages. The approach is correct and addresses the root cause.

✅ Positive Changes

  • Correct state path: Using state.values.recentMessages is the right approach for cross-provider compatibility
  • Simplified implementation: Removes unnecessary runtime parameter and delegates message formatting to providers
  • Consistent updates: All action handlers updated correctly
  • Version bump: Appropriate patch version increment

⚠️ Issues Found

1. Type Safety (Medium Priority)

Location: src/utils/context.ts:5

The current type check uses a ternary that silently converts non-string values to empty string:

const recentMessages = typeof raw === 'string' ? raw : '';

This could hide bugs. If a provider accidentally sets recentMessages to an array or object, the plugin silently fails. Consider explicit validation that fails fast in development.

2. Edge Case Handling (Low Priority)

Location: src/utils/context.ts:11

If message.content.text is empty/undefined but recentMessages exists, the function returns an awkward trailing "Current request: " with no actual request. Consider handling this edge case explicitly.

3. Missing Test Coverage (Medium Priority)

Location: __tests__/unit/context.test.ts

The tests do not verify:

  • Behavior when state.values.recentMessages is not a string (type mismatch)
  • Behavior when message.content.text is empty but recentMessages exists
  • Very long recentMessages strings (potential token limits)

These edge cases could cause production issues.

🔒 Security Assessment

No security issues found. The changes:

  • Do not introduce injection vulnerabilities
  • Do not expose sensitive data
  • Do not change authentication/authorization logic
  • Properly handle user input

📋 Recommendations

  1. Add explicit type validation for state.values.recentMessages
  2. Handle empty message.content.text with existing recent messages
  3. Add test cases for edge cases mentioned above
  4. Consider documenting the expected format/structure of state.values.recentMessages

Overall Assessment

The PR solves the immediate cloud compatibility problem correctly. The suggested improvements are about making the code more robust for edge cases and easier to debug. The changes are safe to merge, but addressing the type safety and test coverage issues would improve long-term maintainability.

Recommendation: Approve with suggested improvements for a future PR.

@standujar standujar force-pushed the fix/conversation-context-state-path branch from 6b5c542 to cfb87ce Compare February 3, 2026 22:19
@claude
Copy link

claude bot commented Feb 3, 2026

Code Review Summary

Overall Assessment: The fix correctly addresses the cloud compatibility issue by switching from state.data.recentMessages to state.values.recentMessages. The approach is sound and aligns with the provider architecture.

Positive Changes:

  • ✅ Correctly uses state.values.recentMessages for cross-provider compatibility
  • ✅ Removes unused runtime parameter, simplifying the function signature
  • ✅ Simplifies tests to match the new implementation
  • ✅ Preserves provider-specific formatting (important for flexibility)

Issues Identified:

  1. Type Safety: Missing null check for message.content (see inline comment)
  2. Empty String Logic: Falsy check on line 8 treats empty strings as missing data
  3. Test Coverage: Missing edge case tests for undefined content

Recommendations:

  • Add null safety for message.content
  • Consider changing the empty check to be more explicit
  • Add integration test to verify behavior on actual cloud provider

Security: No security concerns identified. The change actually improves security by preventing potential null reference errors.

export function buildConversationContext(message: Memory, state: State | undefined): string {
const raw = state?.values?.recentMessages;
const recentMessages = typeof raw === 'string' ? raw : '';
const currentText = message.content?.text ?? '';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Issue: message.content could be undefined according to the Memory type from @elizaos/core. This would cause message.content?.text to be evaluated but message.content.text is accessed without the optional chaining on line 6.

Suggested change
const currentText = message.content?.text ?? '';
const currentText = (message.content?.text ?? '') as string;

This ensures type safety even if message.content is undefined.


if (recentMessages.length === 0) {
return message.content.text || '';
if (!recentMessages) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential Logic Bug: This condition treats an empty string "" as falsy and returns only currentText. This means if the provider returns an empty string for recentMessages (instead of undefined), the context won't include "Current request:" prefix.

Consider making this check more explicit:

Suggested change
if (!recentMessages) {
if (recentMessages === '' || recentMessages === undefined) {

Or alternatively, if empty string should be treated the same as missing:

Suggested change
if (!recentMessages) {
if (!recentMessages || recentMessages.trim() === '') {

This makes the intention clearer and prevents potential bugs with different providers.

@claude
Copy link

claude bot commented Feb 3, 2026

Code Review Summary

I've reviewed this PR which fixes cloud compatibility by switching from state.data.recentMessages to state.values.recentMessages. The changes look solid overall with good test coverage and consistent application across all actions.

✅ Positive Changes

  1. Correct fix for cloud compatibility - Moving from state.data.recentMessages to state.values.recentMessages is the right approach for cross-provider compatibility
  2. Simplified function signature - Removing the unused runtime parameter improves code clarity
  3. Comprehensive test updates - Tests now correctly reflect the new behavior and cover edge cases
  4. Consistent application - All 5 action files were updated consistently

Issues Found

I've posted inline comments on specific concerns:

  • Type safety issue with optional chaining
  • Edge case handling for non-string values
  • Test coverage gap

Security

No security issues identified. The changes don't introduce any new attack vectors and properly handle user input.

Recommendations

  1. Consider adding runtime type validation for state.values.recentMessages
  2. Add a test case for when state.values.recentMessages is a non-string value
  3. Consider documenting the expected format of state.values.recentMessages in code comments

test("handles undefined state", () => {
const runtime = createMockRuntime();
const message = createMockMessage({ content: { text: "Hello" } });
test('handles undefined state', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Edge Case Test: Consider adding a test for when message.content itself is undefined, not just when content.text is empty:

test('handles undefined message content', () => {
  const message = createMockMessage({ content: undefined });
  const state = createMockState();

  const result = buildConversationContext(message, undefined);
  expect(result).toBe('');
});

This would ensure the type safety issue mentioned in context.ts:6 is covered.

export function buildConversationContext(message: Memory, state: State | undefined): string {
const raw = state?.values?.recentMessages;
const recentMessages = typeof raw === 'string' ? raw : '';
const currentText = message.content?.text ?? '';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Safety Concern: The use of message.content?.text with optional chaining is good, but message.content could potentially be undefined in the type system. Consider adding a guard or making the type contract more explicit.

However, looking at the usage in the codebase, all callers cast to Content type first (e.g., message.content as Content in createWorkflow.ts:268), so this is defensive programming which is good practice.

test("includes recent messages in context", () => {
const runtime = createMockRuntime({ agentId: "agent-001" });
const message = createMockMessage({ content: { text: "Activate it" } });
test('appends current request to recentMessages', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage Gap: Consider adding a test for when state.values.recentMessages is an empty string (not undefined):

test('handles empty string recentMessages', () => {
  const message = createMockMessage({ content: { text: 'Test' } });
  const state = createMockState({
    values: { recentMessages: '' },
  });

  const result = buildConversationContext(message, state);
  expect(result).toBe('Test');
});

This would clarify the expected behavior for the falsy check on line 8 of context.ts.

): string {
const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
const raw = state?.values?.recentMessages;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edge Case - Type Safety: While typeof raw === 'string' is a good runtime check, consider what happens if state.values.recentMessages exists but is not a string (e.g., it's an object or array due to a provider bug or version mismatch).

Current behavior: Falls back to empty string, which is safe but silent. Consider logging a warning if the value exists but has an unexpected type:

const raw = state?.values?.recentMessages;
if (raw !== undefined && typeof raw !== 'string') {
  logger.warn('state.values.recentMessages has unexpected type:', typeof raw);
}
const recentMessages = typeof raw === 'string' ? raw : '';

This would help catch provider contract violations during development.

): string {
const recentMessages = (state?.data?.recentMessages as Memory[]) || [];
export function buildConversationContext(message: Memory, state: State | undefined): string {
const raw = state?.values?.recentMessages;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript Best Practice: The type check here is good, but consider using a type guard for better type safety throughout the function:

const raw = state?.values?.recentMessages;
const recentMessages = typeof raw === 'string' ? raw : '';

This is already implemented correctly. Good defensive coding! ✅

However, if state.values.recentMessages could legitimately be other types (number, object, etc.), you might want to add logging when it's not a string to help debug configuration issues in different providers.


if (recentMessages.length === 0) {
return message.content.text || '';
if (!recentMessages) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic Question: Should an empty string be treated the same as undefined?

Currently, if (!recentMessages) will be true for both undefined and '' (empty string). This means if a provider sets state.values.recentMessages = '', you'd return just the current text without the "Current request:" prefix.

Is this intended behavior? If so, it's fine. If not, consider:

if (!recentMessages || recentMessages.length === 0) {

or be explicit about what you're checking.

const userId = message.entityId;
const cacheKey = `workflow_draft:${userId}`;
const generationContext = buildConversationContext(runtime, message, state);
const generationContext = buildConversationContext(message, state);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct Usage: The runtime parameter has been correctly removed from the buildConversationContext call. This is the critical fix that enables cloud provider compatibility.

Verification: All 6 actions (createWorkflow, activateWorkflow, deactivateWorkflow, deleteWorkflow, getExecutions) have been updated consistently. Good job!

test("includes recent messages in context", () => {
const runtime = createMockRuntime({ agentId: "agent-001" });
const message = createMockMessage({ content: { text: "Activate it" } });
test('appends current request to recentMessages', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage - Missing Case: Consider adding a test for when state.values.recentMessages exists but is not a string type. This would verify the type guard on line 5 of context.ts works correctly.

Example:

test('handles non-string recentMessages gracefully', () => {
  const message = createMockMessage({ content: { text: 'Hello' } });
  const state = createMockState({
    values: { recentMessages: { invalid: 'object' } },
  });
  
  const result = buildConversationContext(message, state);
  expect(result).toBe('Hello'); // Should fall back to just the message
});

createMockMessage,
createMockState,
} from "../helpers/mockRuntime";
import { describe, test, expect } from 'bun:test';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor - Code Style: Good job removing the unused createMockRuntime import. The test cleanup is thorough and maintains good coverage.

entityId: "user-001",
content: { text: `Message ${i}` },
}));
test('preserves recentMessages formatting from provider', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent Test: This test is critical for ensuring the fix works correctly. It verifies that:

  1. Provider-formatted messages are preserved as-is (no re-parsing or reformatting)
  2. The "Current request" is properly appended
  3. Different formatting styles from different providers are supported

This is a key requirement for cross-provider compatibility. Great addition!

const userId = message.entityId;
const cacheKey = `workflow_draft:${userId}`;
const generationContext = buildConversationContext(runtime, message, state);
const generationContext = buildConversationContext(message, state);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Practice: This is the most critical usage of buildConversationContext since it's used for workflow generation. The fix ensures that the LLM receives the actual conversation context (user's request) instead of a confirmation message like "yes" or "ok".

The PR description correctly identifies this as fixing workflows being generated from confirmation messages on cloud - this change directly addresses that issue.

{
"name": "@elizaos/plugin-n8n-workflow",
"version": "1.0.10",
"version": "1.0.11",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version Bump: Patch version bump (1.0.10 → 1.0.11) is appropriate for a bug fix. Good adherence to semantic versioning.

@claude
Copy link

claude bot commented Feb 3, 2026

Architecture Notes

Provider Abstraction Pattern: This PR demonstrates good understanding of the ElizaOS provider pattern:

  • Before: Plugin assumed state.data.recentMessages structure from official bootstrap provider
  • After: Plugin relies on standardized state.values.recentMessages string format

This is the correct approach for plugin development - plugins should:

  1. ✅ Use state.values for formatted, provider-agnostic data
  2. ❌ Avoid direct access to state.data (provider-specific structures)
  3. ✅ Let providers handle message formatting/structuring

Deployment Recommendation:
Once the type safety issues are addressed, this should be deployed and tested on cloud to verify:

  • Messages are correctly extracted from the cloud provider's state
  • Workflow generation receives appropriate context
  • The "Current request" properly reflects user intent (not just confirmation messages)

Version Bump:
The version bump from 1.0.10 → 1.0.11 is appropriate for a bug fix release. ✅

@standujar standujar merged commit 638ec78 into main Feb 3, 2026
7 checks passed
@standujar standujar deleted the fix/conversation-context-state-path branch February 3, 2026 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant